
package org.jdamico.socks.server.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import org.jdamico.socks.server.commons.Configs;
import org.jdamico.socks.server.commons.DebugLog;
import org.jdamico.socks.server.commons.Utils;

public class ProxyHandler implements Runnable {
	protected Object mLock;

	Socks4Impl comm = null;

	protected Thread mTheThread = null;

	public Socket mClientSocket = null;
	public Socket mServerSocket = null;

	public byte[] mBuffer = null;

	public InputStream mClientInput = null;
	public OutputStream mClientOutput = null;

	public InputStream mServerInput = null;
	public OutputStream mServerOutput = null;

	public ProxyHandler(Socket clientSocket) {
		setSynchorizedLock(this);

		mClientSocket = clientSocket;
		if (mClientSocket != null) {
			try {
				mClientSocket.setSoTimeout(Configs.DEFAULT_PROXY_TIMEOUT);
			} catch (SocketException e) {
				DebugLog.getInstance().error("Socket Exception during seting Timeout.");
			}
		}

		mBuffer = new byte[Configs.DEFAULT_BUF_SIZE];

		DebugLog.getInstance().println("Proxy Created.");
	}

	public void setSynchorizedLock(Object lock) {
		this.mLock = lock;
	}

	public void start() {
		mTheThread = new Thread(this);
		mTheThread.start();
		DebugLog.getInstance().println("Proxy Started.");
	}

	public void stop() {

		try {
			if (mClientSocket != null)
				mClientSocket.close();
			if (mServerSocket != null)
				mServerSocket.close();
		} catch (IOException e) {
		}
		mClientSocket = null;
		mServerSocket = null;
		DebugLog.getInstance().println("Proxy Stopped.");
		mTheThread.interrupt();
	}

	public void run() {
		if (!prepareClientIoStream()) {
			DebugLog.getInstance().error("Proxy - client socket is null !");
			return;
		}
		handleRelay();
		close();
	}

	public void close() {
		try {
			if (mClientOutput != null) {
				mClientOutput.flush();
				mClientOutput.close();
			}
		} catch (IOException e) {
		}
		try {
			if (mServerOutput != null) {
				mServerOutput.flush();
				mServerOutput.close();
			}
		} catch (IOException e) {
		}
		try {
			if (mClientSocket != null) {
				mClientSocket.close();
			}
		} catch (IOException e) {
		}
		try {
			if (mServerSocket != null) {
				mServerSocket.close();
			}
		} catch (IOException e) {
		}
		mServerSocket = null;
		mClientSocket = null;
		DebugLog.getInstance().println("Proxy Closed.");
	}

	public void sendToClient(byte[] buffer) {
		sendToClient(buffer, buffer.length);
	}

	public void sendToClient(byte[] buffer, int len) {
		if (mClientOutput == null)
			return;
		if (len <= 0 || len > buffer.length)
			return;

		try {
			mClientOutput.write(buffer, 0, len);
			mClientOutput.flush();
		} catch (IOException e) {
			DebugLog.getInstance().error("Sending data to client");
		}
	}

	public void sendToServer(byte[] buffer) {
		sendToServer(buffer, buffer.length);
	}

	public void sendToServer(byte[] buffer, int len) {
		if (mServerOutput == null)
			return;
		if (len <= 0 || len > buffer.length)
			return;

		try {
			mServerOutput.write(buffer, 0, len);
			mServerOutput.flush();
		} catch (IOException e) {
			DebugLog.getInstance().error("Sending data to server");
		}
	}

	public boolean isActive() {
		return (mClientSocket != null && mServerSocket != null);
	}

	public void connectToServer(String server, int port) throws IOException, UnknownHostException {

		if (server.equals("")) {
			close();
			DebugLog.getInstance().error("Invalid Remote Host Name - Empty String !!!");
			return;
		}

		mServerSocket = new Socket(server, port);
		mServerSocket.setSoTimeout(Configs.DEFAULT_PROXY_TIMEOUT);

		DebugLog.getInstance().println("Connected to " + Utils.getSocketInfo(mServerSocket));
		prepareServer();
	}

	protected void prepareServer() throws IOException {
		synchronized (mLock) {
			mServerInput = mServerSocket.getInputStream();
			mServerOutput = mServerSocket.getOutputStream();
		}
	}

	public boolean prepareClientIoStream() {
		if (mClientSocket == null)
			return false;

		try {
			mClientInput = mClientSocket.getInputStream();
			mClientOutput = mClientSocket.getOutputStream();
		} catch (IOException e) {
			DebugLog.getInstance().error("Proxy - can't get I/O streams!");
			DebugLog.getInstance().error(e);
			return false;
		}
		return true;
	}

	public void handleRelay() {

		try {
			byte SOCKS_Version = readByteFromClient();

			switch (SOCKS_Version) {
			case Configs.SOCKS4_Version:
				comm = new Socks4Impl(this);
				break;
			case Configs.SOCKS5_Version:
				comm = new Socks5Impl(this);
				break;
			default:
				DebugLog.getInstance().error("无效的Socks版本 : " + SOCKS_Version);

				return;
			}
			DebugLog.getInstance().println("客户端请求的的Socks版本 " + SOCKS_Version + " Request.");

			comm.authenticate(SOCKS_Version);

			comm.checkClientCommand();

			switch (comm.mSocksCommand) {
			case Configs.SC_CONNECT:
				comm.connect();
				relay();
				break;

			case Configs.SC_BIND:
				comm.bind();
				relay();
				break;

			case Configs.SC_UDP:
				comm.udp();
				break;
			}
		} catch (Exception e) {
			DebugLog.getInstance().error(e);
		}
	}

	public byte readByteFromClient() throws Exception {
		int b;
		while (mClientSocket != null) {

			try {
				b = mClientInput.read();
			} catch (InterruptedIOException e) {
				Thread.yield();
				continue;
			}

			return (byte) b; // return loaded byte

		} // while...
		throw new Exception("Interrupted Reading GetByteFromClient()");
	} // GetByteFromClient()...

	public void relay() {

		boolean isActive = true;
		int dlen = 0;

		while (isActive) {

			// ---> Check for client data <---

			dlen = checkClientData();

			if (dlen < 0)
				isActive = false;
			if (dlen > 0) {
				logClientData(dlen);
				sendToServer(mBuffer, dlen);
			}

			// ---> Check for Server data <---
			dlen = checkServerData();

			if (dlen < 0) {
				isActive = false;
			}
			if (dlen > 0) {
				logServerData(dlen);
				sendToClient(mBuffer, dlen);
			}

			// Thread.currentThread();
			Thread.yield();
		} // while
	}

	public int checkClientData() {
		synchronized (mLock) {
			// The client side is not opened.
			if (mClientInput == null)
				return -1;

			int dlen = 0;

			try {
				dlen = mClientInput.read(mBuffer, 0, Configs.DEFAULT_BUF_SIZE);
			} catch (InterruptedIOException e) {
				return 0;
			} catch (IOException e) {
				DebugLog.getInstance().println("Client connection Closed!");
				close(); // Close the server on this exception
				return -1;
			}

			if (dlen < 0)
				close();

			return dlen;
		}
	}

	public int checkServerData() {
		synchronized (mLock) {
			// The client side is not opened.
			if (mServerInput == null)
				return -1;

			int dlen = 0;

			try {
				dlen = mServerInput.read(mBuffer, 0, Configs.DEFAULT_BUF_SIZE);
			} catch (InterruptedIOException e) {
				return 0;
			} catch (IOException e) {
				DebugLog.getInstance().println("Server connection Closed!");
				close(); // Close the server on this exception
				return -1;
			}

			if (dlen < 0)
				close();

			return dlen;
		}
	}

	public void logServerData(int traffic) {
		DebugLog.getInstance()
				.println("Srv data : " + Utils.getSocketInfo(mClientSocket) + " << <" + comm.mServer.getHostName() + "/"
						+ comm.mServer.getHostAddress() + ":" + comm.mServerPort + "> : " + traffic + " bytes.");
	}

	public void logClientData(int traffic) {
		DebugLog.getInstance()
				.println("Cli data : " + Utils.getSocketInfo(mClientSocket) + " >> <" + comm.mServer.getHostName() + "/"
						+ comm.mServer.getHostAddress() + ":" + comm.mServerPort + "> : " + traffic + " bytes.");
	}

	public Socket getSocksServer() {
		return mServerSocket;
	}

}
